using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using AntEater.Runtime;

namespace AntEater.Integration.Editors
{
    /// <summary>
    /// Provides an Editor Factory for build files
    /// </summary>
    [Guid(Guids.BuildFileEditorFactoryId)]
    [CLSCompliant(false)]
    public sealed class BuildFileEditorFactory : IVsEditorFactory, IDisposable
    {
        private ServiceProvider _serviceProvider;
        private Package _package;
        private IAntEaterRuntime _runtime;

        /// <summary>
        /// Initializes a new instance of the <see cref="BuildFileEditorFactory"/> class.
        /// </summary>
        /// <param name="package">The package.</param>
        /// <param name="runtime">The runtime.</param>
        public BuildFileEditorFactory(Package package, IAntEaterRuntime runtime) {
            this._package = package;
            this._runtime = runtime;
        }

        /// <summary>
        /// Maps the logical view.
        /// </summary>
        /// <param name="rguidLogicalView">The id of the logical view.</param>
        /// <param name="pbstrPhysicalView">The name of the physical view.</param>
        /// <returns></returns>
        int IVsEditorFactory.MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView) {

            int result = VSConstants.E_NOTIMPL;
            pbstrPhysicalView = null;

            // Return OK for Primary an Designer view
            if ((rguidLogicalView == VSConstants.LOGVIEWID_Primary) || (rguidLogicalView == VSConstants.LOGVIEWID_Designer)) {
                result = VSConstants.S_OK;
            }

            return result;
        }

        /// <summary>
        /// Creates the editor instance.
        /// </summary>
        /// <param name="grfCreateDoc">The GRF create doc.</param>
        /// <param name="pszMkDocument">The PSZ mk document.</param>
        /// <param name="pszPhysicalView">The PSZ physical view.</param>
        /// <param name="pvHier">The pv hier.</param>
        /// <param name="itemid">The itemid.</param>
        /// <param name="punkDocDataExisting">The punk doc data existing.</param>
        /// <param name="ppunkDocView">The ppunk doc view.</param>
        /// <param name="ppunkDocData">The ppunk doc data.</param>
        /// <param name="pbstrEditorCaption">The PBSTR editor caption.</param>
        /// <param name="pguidCmdUI">The pguid CMD UI.</param>
        /// <param name="pgrfCDW">The PGRF CDW.</param>
        /// <returns></returns>
        int IVsEditorFactory.CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW) {

            // initialize varaibles
            ppunkDocView = IntPtr.Zero;
            ppunkDocData = IntPtr.Zero;
            pguidCmdUI = Guids.BuildFileEditorFactoryIdGuid;
            pgrfCDW = 0;
            pbstrEditorCaption = null;

            int result = VSConstants.E_INVALIDARG;

            if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) != 0) {

                // Get text lines of the file
                IVsTextLines textLines = this.GetTextBuffer(punkDocDataExisting);

                // Create the Designer view
                BuildFileEditorPane editorPane = new BuildFileEditorPane(_runtime, pszMkDocument, textLines);
                ppunkDocView = Marshal.GetIUnknownForObject(editorPane);
                ppunkDocData = Marshal.GetIUnknownForObject(textLines);
                pbstrEditorCaption = Strings.BuildFileEditorCaption;

                result = VSConstants.S_OK;

            }

            return result;
        }

        /// <summary>
        /// Sets the site.
        /// </summary>
        /// <param name="psp">The PSP.</param>
        /// <returns></returns>
        int IVsEditorFactory.SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp) {
            _serviceProvider = new ServiceProvider(psp);
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Closes this instance.
        /// </summary>
        /// <returns></returns>
        int IVsEditorFactory.Close() {
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Gets the text buffer.
        /// </summary>
        /// <param name="docDataExisting">The doc data existing.</param>
        /// <returns></returns>
        private IVsTextLines GetTextBuffer(IntPtr docDataExisting) {

            IVsTextLines textLines;

            // Check if doc data allready exists
            if (docDataExisting == IntPtr.Zero) {
                // Create a new IVsTextLines buffer.
                Type textLinesType = typeof(IVsTextLines);
                Guid riid = textLinesType.GUID;
                Guid clsid = typeof(VsTextBufferClass).GUID;
                textLines = (IVsTextLines)_package.CreateInstance(ref clsid, ref riid, textLinesType);

                // Set the buffer's site
                ((IObjectWithSite)textLines).SetSite(_serviceProvider.GetService(typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider)));
            } else {
                // Use the existing text buffer
                textLines = (IVsTextLines)Marshal.GetObjectForIUnknown(docDataExisting);
            }

            return textLines;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose() {
            Dispose(true);
        }

        /// <summary>
        /// Disposes the specified disposing.
        /// </summary>
        /// <param name="disposing">if set to <c>true</c> [disposing].</param>
        private void Dispose(bool disposing) {
            lock (this) {
                if (disposing) {
                    if (_serviceProvider != null) {
                        _serviceProvider.Dispose();
                        _serviceProvider = null;
                    }
                }
            }
        }
    }
}
